package com.hdlang.assists.ad.utils

import android.content.Context
import android.graphics.Rect
import android.os.Build
import android.text.TextUtils
import android.view.accessibility.AccessibilityNodeInfo
import com.hdlang.assists.ad.data.DataElement
import com.hdlang.assists.ad.data.WidgetQueryEnum
import com.ven.assists.AssistsCore
import com.ven.assists.AssistsCore.des
import com.ven.assists.AssistsCore.getBoundsInScreen
import com.ven.assists.AssistsCore.setNodeText
import com.ven.assists.AssistsCore.txt
import com.ven.assists.utils.NodeClassValue
import kotlinx.coroutines.delay
import java.util.Locale
import kotlin.math.abs

object GestureUtils {
    private var width: Int = 0
    private var height: Int = 0
    fun updateScreenSize(context: Context) {
        val displayMetrics = context.resources.displayMetrics
        CacheUtils.setInt(Constants.KEY_WIDTH, displayMetrics.widthPixels)
        CacheUtils.setInt(Constants.KEY_HEIGHT, displayMetrics.heightPixels)
    }

    private fun getWidth(): Int {
        if (width > 0) {
            return width
        }
        width = CacheUtils.getInt(Constants.KEY_WIDTH, Constants.WIDTH)
        return width
    }

    private fun getHeight(): Int {
        if (height > 0) {
            return height
        }
        height = CacheUtils.getInt(Constants.KEY_HEIGHT, Constants.HEIGHT)
        return height
    }

    private suspend fun randomClick(rect: Rect) {
        val x = RandomUtils.getRandomX(rect)
        val y = RandomUtils.getRandomY(rect)
        AssistsCore.gestureClick(x.toFloat(), y.toFloat(), (100 + RandomUtils.random(200)).toLong())
    }

    suspend fun swipeUp(
        x1Start: Number = 0.4,
        x1End: Number = 0.6,
        y1Start: Number = 0.6,
        y1End: Number = 0.9,
        x2Start: Number = 0.4,
        x2End: Number = 0.6,
        y2Start: Number = 0.1,
        y2End: Number = 0.3,
        durationStart: Int = 600,
        durationEnd: Int = 2000,
        runDelay: Boolean = true,
        delayStart: Int = 600,
        delayEnd: Int = 2000
    ): Boolean {
        return swipe(
            x1Start = x1Start,
            x1End = x1End,
            y1Start = y1Start,
            y1End = y1End,
            x2Start = x2Start,
            x2End = x2End,
            y2Start = y2Start,
            y2End = y2End,
            durationStart = durationStart,
            durationEnd = durationEnd,
            runDelay = runDelay,
            delayStart = delayStart,
            delayEnd = delayEnd,
        )
    }

    suspend fun swipeDown(
        x1Start: Number = 0.4,
        x1End: Number = 0.6,
        y1Start: Number = 0.1,
        y1End: Number = 0.3,
        x2Start: Number = 0.4,
        x2End: Number = 0.6,
        y2Start: Number = 0.6,
        y2End: Number = 0.9,
        durationStart: Int = 600,
        durationEnd: Int = 2000,
        runDelay: Boolean = true,
        delayStart: Int = 600,
        delayEnd: Int = 2000
    ): Boolean {
        return swipe(
            x1Start = x1Start,
            x1End = x1End,
            y1Start = y1Start,
            y1End = y1End,
            x2Start = x2Start,
            x2End = x2End,
            y2Start = y2Start,
            y2End = y2End,
            durationStart = durationStart,
            durationEnd = durationEnd,
            runDelay = runDelay,
            delayStart = delayStart,
            delayEnd = delayEnd,
        )
    }

    private suspend fun swipe(
        x1Start: Number,
        x1End: Number,
        y1Start: Number,
        y1End: Number,
        x2Start: Number,
        x2End: Number,
        y2Start: Number,
        y2End: Number,
        durationStart: Int,
        durationEnd: Int,
        runDelay: Boolean,
        delayStart: Int,
        delayEnd: Int
    ): Boolean {
        val width = getWidth()
        val height = getHeight()
        val x1 = (x1Start.toDouble() +
                RandomUtils.random(abs(((x1End.toDouble() - x1Start.toDouble()) * 100).toInt())) / 100.0) * width.toInt()
        val y1 = (y1Start.toDouble() +
                RandomUtils.random(abs(((y1End.toDouble() - y1Start.toDouble()) * 100).toInt())) / 100.0) * height.toInt()
        val x2 = (x2Start.toDouble() +
                RandomUtils.random(abs(((x2End.toDouble() - x2Start.toDouble()) * 100).toInt())) / 100.0) * width.toInt()
        val y2 = (y2Start.toDouble() +
                RandomUtils.random(abs(((y2End.toDouble() - y2Start.toDouble()) * 100).toInt())) / 100.0) * height.toInt()
        val duration = durationStart + RandomUtils.random(abs(durationEnd - durationStart))
        val result = AssistsCore.gesture(
            floatArrayOf(x1.toFloat(), y1.toFloat()),
            floatArrayOf(x2.toFloat(), y2.toFloat()),
            0,
            duration.toLong()
        )
        if (result) {
            if (runDelay) {
                widgetDelay(delayStart, delayEnd)
            }
            return true
        }
        return false
    }

    suspend fun setInput(
        value: String?, timeout: Int = 3000,
    ): Boolean {
        if (value?.isNotEmpty() == true) {
            return tryBoolean {
                val node = findWidget(
                    dataElement = DataElement(
                        textStart = "搜索框",
                        timeout = timeout,
                    ),
                )
                if (node != null) {
                    node.setNodeText(value)
                    val r = widgetTimeout(timeout) {
                        val widget = findWidget(
                            dataElement = DataElement(
                                textStart = "搜索框",
                                timeout = timeout,
                            ),
                        )
                        delay(100)
                        val refreshValue = widget?.txt()
                        LogUtils.d("搜索框的值，${refreshValue}")
                        return@widgetTimeout refreshValue?.endsWith(value) == true
                    }
                    if (r) {
                        return@tryBoolean true
                    }
                }
                return@tryBoolean false
            }

        }
        return false
    }

    suspend fun setInput2(
        value: String?,
        depth: Int? = null,
        drawingOrder: Int? = null,
        timeout: Int = 3000,
    ): Boolean {
        if (value?.isNotEmpty() == true) {
            return tryBoolean {
                val node = findWidget(
                    dataElement = DataElement(
                        className = NodeClassValue.EditText,
                        depth = depth,
                        drawingOrder = drawingOrder,
                        timeout = timeout,
                    ),
                )
                if (node != null) {
                    node.setNodeText(value)
                    val r = widgetTimeout(timeout) {
                        val widget = findWidget(
                            dataElement = DataElement(
                                className = NodeClassValue.EditText,
                                depth = depth,
                                drawingOrder = drawingOrder,
                                timeout = timeout,
                            ),
                        )
                        delay(100)
                        val refreshValue = widget?.txt()
                        LogUtils.d("搜索框的值，${refreshValue}")
                        return@widgetTimeout refreshValue?.equals(value) == true
                    }
                    if (r) {
                        return@tryBoolean true
                    }
                }
                return@tryBoolean false
            }

        }
        return false
    }

    suspend fun click(
        dataElement: DataElement,
    ): Boolean {
        val node = findWidget(dataElement = dataElement)
        val rect = node?.getBoundsInScreen()
        if (rect != null) {
            randomClick(rect)
            if (dataElement.runDelay) {
                widgetDelay(start = Constants.CLICK_DELAY_BEGIN, end = Constants.CLICK_DELAY_END)
            }
            return true
        }
        return false
    }

    suspend fun existElement(
        dataElement: DataElement
    ): Boolean {
        val node = findWidget(dataElement = dataElement)
        return node != null
    }

    fun getNodeDepth(node: AccessibilityNodeInfo): Int {
        var depth = 0
        var parent = node.parent

        while (parent != null && depth < 100) {
            depth++
            val nextParent = parent.parent
            parent.recycle() // 回收当前父节点
            parent = nextParent
        }


        // 如果循环因达到最大深度而终止，回收剩余的父节点
        parent?.recycle()

        return depth
    }

    private suspend fun filterClassName(
        infos: List<AccessibilityNodeInfo>,
        className: String
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        if (className.isNotEmpty()) {
            for (info in infos) {
                if (info.className == className) {
                    nodes.add(info)
                }
            }
        } else {
            nodes.addAll(infos)
        }
        return nodes
    }

    private suspend fun filterText(
        infos: List<AccessibilityNodeInfo>,
        text: String
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        if (text.isNotEmpty()) {
            for (info in infos) {
                if (info.txt() == text) {
                    nodes.add(info)
                }
            }
        } else {
            nodes.addAll(infos)
        }
        return nodes
    }

    private suspend fun filterTextMatch(
        infos: List<AccessibilityNodeInfo>,
        textStart: String,
        textEnd: String
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        if (textStart.isEmpty() && textEnd.isEmpty()) {
            nodes.addAll(infos)
        } else {
            for (info in infos) {
                val text = info.txt()
                if (text.isNotEmpty()) {
                    if (textStart.isNotEmpty() && textEnd.isNotEmpty()) {
                        if (text.startsWith(textStart) && text.endsWith(textEnd)) {
                            nodes.add(info)
                        }
                    } else if (textStart.isNotEmpty() || textEnd.isNotEmpty()) {
                        if ((text.startsWith(textStart) && textStart.isNotEmpty()) ||
                            (text.endsWith(textEnd) && textEnd.isNotEmpty())) {
                            nodes.add(info)
                        }
                    }
                }
            }
        }
        return nodes
    }

    private suspend fun filterDesc(
        infos: List<AccessibilityNodeInfo>,
        desc: String
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        if (desc.isNotEmpty()) {
            for (info in infos) {
                if (info.txt() == desc) {
                    nodes.add(info)
                }

            }
        } else {
            nodes.addAll(infos)
        }
        return nodes
    }

    private suspend fun filterDescMatch(
        infos: List<AccessibilityNodeInfo>,
        descStart: String,
        descEnd: String
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        if (descStart.isEmpty() && descEnd.isEmpty()) {
            nodes.addAll(infos)
        } else {
            for (info in infos) {
                val text = info.des()
                if (text.isNotEmpty()) {
                    if (descStart.isNotEmpty() && descEnd.isNotEmpty()) {
                        if (text.startsWith(descStart) && text.endsWith(descEnd)) {
                            nodes.add(info)
                        }
                    } else if (descStart.isNotEmpty() || descEnd.isNotEmpty()) {
                        if ((descStart.isNotEmpty() && text.startsWith(descStart)) || (
                                    descEnd.isNotEmpty() &&
                                            text.endsWith(
                                                descEnd
                                            )
                                    )
                        ) {
                            nodes.add(info)
                        }
                    }
                }
            }
        }
        return nodes
    }

    private suspend fun filterDrawingOrder(
        infos: List<AccessibilityNodeInfo>,
        drawingOrder: Int?
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        if (drawingOrder != null) {
            for (info in infos) {
                if (info.drawingOrder == drawingOrder) {
                    nodes.add(info)
                }
            }
            return nodes
        } else {
            nodes.addAll(infos)
        }
        return nodes
    }

    private suspend fun filterDepth(
        infos: List<AccessibilityNodeInfo>,
        depth: Int?
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        if (depth != null) {
            for (info in infos) {
                val nodeDepth = getNodeDepth(info)
                if (nodeDepth == depth) {
                    nodes.add(info)
                }
            }
            return nodes
        } else {
            nodes.addAll(infos)
        }
        return nodes
    }

    private suspend fun filterVisibleToUser(
        infos: List<AccessibilityNodeInfo>,
    ): List<AccessibilityNodeInfo> {
        val nodes = ArrayList<AccessibilityNodeInfo>()
        for (info in infos) {
            if (info.isVisibleToUser) {
                nodes.add(info)
            }
        }
        return nodes
    }

    suspend fun findWidget(
        dataElement: DataElement
    ): AccessibilityNodeInfo? {
        val nodes = findWidgetTemplate(timeout = dataElement.timeout) {
            var nodeInfos: List<AccessibilityNodeInfo>? = null
            if (dataElement.queryEnum == WidgetQueryEnum.ALL) {
                nodeInfos = AssistsCore.getAllNodes()
            } else if (dataElement.queryEnum == WidgetQueryEnum.SINGLE) {
                val infos = ArrayList<AccessibilityNodeInfo>()
                if (dataElement.id.isNotEmpty()) {
                    val tempList =
                        AssistsCore.findById(id = dataElement.id, text = dataElement.text)
                    infos.addAll(filterClassName(tempList, dataElement.className))
                } else {
                    val tempList = if (dataElement.className.isNotEmpty()) {
                        AssistsCore.findByTags(dataElement.className)
                    } else {
                        AssistsCore.getAllNodes()
                    }
                    infos.addAll(tempList)
                }
                val textNodes = filterText(infos = infos, text = dataElement.text)
                val descNodes = filterDesc(infos = textNodes, desc = dataElement.desc)
                val textMatchNodes = filterTextMatch(
                    infos = descNodes,
                    textStart = dataElement.textStart,
                    textEnd = dataElement.textEnd
                )
                val descMatchNodes = filterDescMatch(
                    infos = textMatchNodes,
                    descStart = dataElement.descStart,
                    descEnd = dataElement.descEnd,
                )
                val depthNodes = filterDepth(infos = descMatchNodes, depth = dataElement.depth)
                val drawingOrderNodes = filterDrawingOrder(
                    infos = depthNodes,
                    drawingOrder = dataElement.drawingOrder
                )
                nodeInfos = drawingOrderNodes
            } else if (dataElement.queryEnum == WidgetQueryEnum.TEXT_NOT_EMPTY) {
                val infos = if (dataElement.className.isNotEmpty()) {
                    AssistsCore.findByTags(dataElement.className)
                } else {
                    AssistsCore.getAllNodes()
                }
                val nodes = ArrayList<AccessibilityNodeInfo>()
                for (info in infos) {
                    val text = info.txt()
                    if (text.isNotEmpty()) {
                        nodes.add(info)
                    }
                }
                nodeInfos = nodes
            }
            val allNodes = ArrayList<AccessibilityNodeInfo>()
            if (nodeInfos?.isNotEmpty() == true) {
                allNodes.addAll(filterVisibleToUser(nodeInfos))
            }
            return@findWidgetTemplate allNodes
        }
        if (nodes.isNotEmpty()) {
            return nodes.first()
        }
        return null
    }

    private suspend fun findWidgetTemplate(
        timeout: Int = 3000,
        tryCount: Int = 3,
        runnable: suspend () -> List<AccessibilityNodeInfo>
    ): List<AccessibilityNodeInfo> {
        val nodes = tryObject<List<AccessibilityNodeInfo>?>(count = tryCount) {
            val startTime = System.currentTimeMillis()
            while (startTime + timeout > System.currentTimeMillis()) {
                val nodes = runnable.invoke()
                if (nodes.isNotEmpty()) {
                    return@tryObject nodes
                }
                delay(100)
            }
            return@tryObject null
        }
        if (nodes?.isNotEmpty() == true) {
            return nodes
        }
        return emptyList()
    }

    suspend fun <T> tryObject(count: Int = 3, runnable: suspend () -> T?): T? {
        for (index in 0..<count) {
            val result = runnable.invoke()
            if (result != null) {
                return result
            }
        }
        return null
    }

    suspend fun tryBoolean(count: Int = 3, runnable: suspend () -> Boolean): Boolean {
        val result = tryObject<Boolean?>(count = count, runnable = runnable)
        if (result != null) {
            return result
        }
        return false
    }

    suspend fun widgetTimeout(timeout: Int = 3000, runnable: suspend () -> Boolean): Boolean {
        val startTime = System.currentTimeMillis()
        while (startTime + timeout > System.currentTimeMillis()) {
            val r = runnable.invoke()
            if (r) {
                return true
            }
            delay(100)
        }
        return false
    }

    suspend fun widgetDelay(start: Int = 300, end: Int = 500) {
        val randomRandom = RandomUtils.random(abs(start - end))
        delay(randomRandom.toLong())
    }

    fun cleanBackgroundElement(): Array<DataElement> {
        LogUtils.d("品牌：${Build.MANUFACTURER}")
        if (TextUtils.equals(Build.MANUFACTURER.lowercase(Locale.getDefault()), "vivo")) {
            return Constants.ELEMENT_CLEAN_BACKGROUND_VIVO
        }
        return Constants.ELEMENT_CLEAN_BACKGROUND_XIAOMI
    }

}